/*
 * Copyrignt (c) xuzeshui.com. All Rights Reserved.
 * Author: Zeshui Xu<i@xuzeshui.com>
 * Created Time: 2015-10-07 21:48:53
 * Last Modified: 2017-05-19 20:42:00
 * File Name: auth/urlsign.go
 * Description:URL签名验证
 */
package auth

import (
	"crypto/md5"
	"fmt"
	"io"
	"math"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"

	"qkstatis/errorcode"

	"github.com/astaxie/beego"
)

var (
	urlSignInstance *urlSignService
)

type urlSignService struct {
	acceptableURLTimestampDelta int64
}

func newURLSignService() *urlSignService {
	service := &urlSignService{}
	service.acceptableURLTimestampDelta = int64(beego.AppConfig.DefaultInt("acceptableURLTimestampDelta", 600))
	return service
}

//获取实例
func GetURLSignService() *urlSignService {
	if urlSignInstance == nil {
		urlSignInstance = newURLSignService()
	}
	return urlSignInstance
}

/*
	URL参数验证策略
*/
func (p *urlSignService) ValidateURLParam(u url.Values) (uint, string, bool) {
	params := u
	//time
	if clientTime, ok := params["time"]; ok {
		if val, err := strconv.Atoi(clientTime[0]); err == nil {
			if !p.IsValidURLTime(int64(val)) {
				return errorcode.EC_GL_INVALID_URL_TIME, "invalid url time", false
			}
		} else { //not interger string
			return errorcode.EC_GL_INVALID_URL_TIME, "invalid url time(not number)", false
		}
	} else { //missing time field
		return errorcode.EC_GL_INVALID_URL_TIME, "invalid url time(missing)", false
	}

	//sign
	if sign, ok := params["sign"]; ok {
		calcatedSign := p.CalcURLSign(u)
		if !p.IsValidURLSign(sign[0], calcatedSign) {
			return errorcode.EC_GL_INVALID_URL_SIGN, "invalid url sign", false
		}
	} else { //missing sign field
		return errorcode.EC_GL_INVALID_URL_SIGN, "invalid url sign", false
	}
	return 0, "", true
}

/*
	URL参数签名算法
*/
func (p *urlSignService) CalcURLSign(u url.Values) string {
	if u == nil {
		return ""
	}
	params := u
	keys := make([]string, 0, len(params)-1)
	appkey := ""
	for key, _ := range params {
		if "sign" == key || "_" == key {
			continue
		} else if "appkey" == key {
			appkey = params[key][0]
		}
		keys = append(keys, key)
	}
	sort.Strings(keys)
	input := make([]string, 0, 2*len(params)-1)
	for _, key := range keys {
		input = append(input, key)
		input = append(input, params[key][0])
	}
	secretKey, ok := GetAuthAppKeyService().GetSecretKey(appkey)
	if !ok {
		return ""
	}

	input = append(input, secretKey)
	inputValue := strings.Join(input, "")
	md5Tool := md5.New()
	io.WriteString(md5Tool, inputValue)
	signCalclated := fmt.Sprintf("%x", md5Tool.Sum(nil))
	//fmt.Println("inputValue: ", inputValue, ", signCalclated: ", signCalclated)
	return signCalclated
}

/*
	判断URL签名是否合法
	参数: sign 为URL中原始签名
		  calcatedSign 为算法计算后的签名
	返回值: true表示合法,false表示非法
*/
func (p *urlSignService) IsValidURLSign(sign, calcatedSign string) bool {
	return sign == calcatedSign
}

func (p *urlSignService) IsValidURLTime(reqTimestamp int64) bool {
	currTimestamp := time.Now().Unix()
	return math.Abs(float64(currTimestamp-reqTimestamp)) <= float64(p.acceptableURLTimestampDelta)
}

/*
	URL参数签名算法
*/
func (p *urlSignService) CalcURLSignByMap(u map[string]interface{}, secretKey string) string {
	if u == nil {
		return ""
	}
	params := u
	keys := make([]string, 0, len(params))
	for key, _ := range params {
		if "sign" == key || "_" == key {
			continue
		}
		keys = append(keys, key)
	}
	sort.Strings(keys)
	input := make([]string, 0, 2*len(params)+1)
	for _, key := range keys {
		input = append(input, key)
		input = append(input, fmt.Sprintf("%v", params[key]))
	}

	input = append(input, secretKey)
	inputValue := strings.Join(input, "")
	md5Tool := md5.New()
	io.WriteString(md5Tool, inputValue)
	signCalclated := fmt.Sprintf("%x", md5Tool.Sum(nil))
	//fmt.Println("inputValue: ", inputValue, ", signCalclated: ", signCalclated)
	return signCalclated
}

/*
func main() {
	u := &url.Values{}
	u.Add("appkey", "shuige")
	u.Add("time", "1444219713")
	u.Add("openid", "abcd123")
	CalcURLSign(u)
}
*/
